﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using LightPersist.Exceptions;
using LightPersist.Metainfo;
using LightPersist.Storage;
using LightPersist.Transactions;

namespace LightPersist
{
	public sealed class LightPersistContext : IDisposable, INotifyPropertyChanged
	{
		static readonly Guid _dbConfigId = Constants.DbConfigObjectId;
		readonly ModelBaseWeakCache _cache = new ModelBaseWeakCache();
		readonly DomainMetainfo _domainMetainfo;
		readonly List<IModelChangingListener> _listeners = new List<IModelChangingListener>();
		readonly IStorage _storage;
		readonly TransactionProcessor _transactionProcessor;
		Transaction _currentTransaction;
		PersistentDatabaseConfiguration _dbConfig;
		bool _isDisposed;

		/// <summary>
		/// </summary>
		/// <param name="storage">e.g. InmemoryStorage, XmlStorage</param>
		/// <param name="metainfo">e.g. for &lt;DomainMetainfo DomainName='MyModel'&gt;
		/// Use MyModelMetainfo.Metainfo</param>
		public LightPersistContext(IStorage storage, DomainMetainfo metainfo)
		{
			LightProfiling.Lap("Context: ctor start...");
			_storage = storage;
			LightProfiling.Lap("Context: trproc...");
			_transactionProcessor = new TransactionProcessor(this);
			LightProfiling.Lap("Context: trproc");
			LightProfiling.Lap("Context: listener...");
			RegisterListener(new NavigationConsistencyListener(this));
			LightProfiling.Lap("Context: listener");
			_domainMetainfo = metainfo;
			LightProfiling.Lap("Context: prepare...");
			_domainMetainfo.Prepare();
			LightProfiling.Lap("Context: prepared");
			LightProfiling.Lap("Context: ctored");
		}

		public DomainMetainfo DomainMetainfo
		{
			get { return _domainMetainfo; }
		}

		public IStorage Storage
		{
			get { return _storage; }
		}

		public bool CanUndo
		{
			get { return TransactionProcessor.CanUndo; }
		}

		public bool CanRedo
		{
			get { return TransactionProcessor.CanRedo; }
		}

		internal ModelBaseWeakCache Cache
		{
			get
			{
				Aspect();
				return _cache;
			}
		}

		TransactionProcessor TransactionProcessor
		{
			get { return _transactionProcessor; }
		}

		Transaction CurrentTransaction
		{
			get { return _currentTransaction ?? NullTransaction.Instance; }
			set { _currentTransaction = value ?? NullTransaction.Instance; }
		}

		/// <summary>
		/// Executes an Action in Begin/Commit transaction block
		/// </summary>
		public Action Execute
		{
			set
			{
				using (var tr = BeginTransaction())
				{
					value();
					tr.CommitTransaction();
				}
			}
		}

		/// <summary>
		/// Persistent database config
		/// </summary>
		internal PersistentDatabaseConfiguration DbConfig
		{
			get
			{
				if (_dbConfig == null)
				{
					// from cache (never be useful, 2 cases: not cached, or cached by _dbConfig)
//					var cached = (PersistentDatabaseConfiguration) Cache.TryGetValue(_dbConfigId);
//					if (cached != null)
//					{
//						return _dbConfig = cached;
//					}

					// try load
					var state = CreateObjectState(typeof(PersistentDatabaseConfiguration), _dbConfigId);
					if (Storage.IsExists(state))
					{
						return _dbConfig = GetObjectByID<PersistentDatabaseConfiguration>(_dbConfigId);
					}

					// create
					var model = CreateModel(typeof(PersistentDatabaseConfiguration), state);
					model.ObjectState.Set(ModelBase.IsRootName, true);
					Storage.CreateObject(model.ObjectState);
					Cache[_dbConfigId] = model;

					// use correct public method to retrieve and prepare this object
					return _dbConfig = GetObjectByID<PersistentDatabaseConfiguration>(_dbConfigId);
				}
				return _dbConfig;
			}
		}

		bool IsTransactionOpened
		{
			get { return !(CurrentTransaction is NullTransaction); }
		}

		#region IDisposable Members

		public void Dispose()
		{
			if (!_isDisposed)
			{
				Cache.Clear();
				_storage.Dispose();
				_isDisposed = true;
			}
		}

		#endregion

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion

		/// <summary>
		/// Call back for customizing process of make readonly object writable
		/// </summary>
		public event EventHandler<WriteRequestedEventArgs> ObjectChangeRequest;

		public void RegisterListener(IModelChangingListener listener)
		{
			if (TransactionProcessor.CanUndo || TransactionProcessor.CanRedo)
			{
				throw new LightPersistException("Can not RegisterListener, some transactions already exists. RegisterListener before any changes in database");
			}
			_listeners.Add(listener);
		}

		bool OnObjectChangeRequest(ModelBase obj)
		{
			var args = new WriteRequestedEventArgs(obj);
			if (ObjectChangeRequest != null)
			{
				ObjectChangeRequest(this, args);
			}
			return args.IsAllowed;
		}

		public void Undo()
		{
			EnsureTransactionClosed();
			TransactionProcessor.PerformUndo();
		}

		public void Redo()
		{
			EnsureTransactionClosed();
			TransactionProcessor.PerformRedo();
		}

		public bool TryUndo()
		{
			if (!CanUndo)
			{
				return false;
			}
			Undo();
			return true;
		}

		public bool TryRedo()
		{
			if (!CanRedo)
			{
				return false;
			}
			Redo();
			return true;
		}

		/// <summary>
		/// Executes an Action in Begin/Commit transaction block. In the case of exeption another Action within 
		/// transaction executes in the cath block.
		/// </summary>
		/// <param name="act">Action to execute</param>
		/// <param name="catchBlock">Action to execute if an exception occures</param>
		public void Try(Action act, Action catchBlock)
		{
			using (var trMain = BeginTransaction())
			{
				try
				{
					using (var tr = BeginTransaction())
					{
						act();
						tr.CommitTransaction();
					}
				}
				catch
				{
					using (var tr = BeginTransaction())
					{
						catchBlock();
						tr.CommitTransaction();
					}
				}
				trMain.CommitTransaction();
			}
		}

		/// <summary>
		/// Create or return the first root class
		/// </summary>
		public T RootClass<T>() where T : ModelBase
		{
			var dbConfig = DbConfig;
			if (dbConfig.RootObjectId == default(Guid))
			{
				Action act = delegate { dbConfig.RootObjectId = DebugHelper.NewGuid(); };
				if (IsTransactionOpened)
				{
					act();
				}
				else
				{
					Execute = act;
				}
			}

			var rootObjectId = dbConfig.RootObjectId;

			var state = CreateObjectState(typeof(T), rootObjectId);
			if (Storage.IsExists(state))
			{
				return GetObjectByID<T>(rootObjectId);
			}

			var model = CreateModel(typeof(T), state);
			model.ObjectState.Set(ModelBase.IsRootName, true);
			Storage.CreateObject(model.ObjectState);
			Cache[rootObjectId] = model;

			return GetObjectByID<T>(rootObjectId);
		}

		internal ValueModelWrapper CreateValueWrapper(object value)
		{
			var wrapper = Create<ValueModelWrapper>();
			wrapper.Value = value;
			return wrapper;
		}

		public T GetObjectByID<T>(Guid guid) where T : ModelBase
		{
			return (T)GetObjectByID(guid, typeof(T));
		}

		public ModelBase GetObjectByID(Guid guid, Type type)
		{
			// TODO improve performance using custom implementation, like commented one, but without bugs. (tests should catch that)
			//if (type == null)
			//{
			return GetObjectByID(guid);
			//}

			//// EnsureTransactionOpenedAndValid();
			//return Cache.GetValue(guid, () =>
			//{
			//   var newobj = CreateModel(type);
			//   var state = newobj.ObjectState;
			//   state.Identity = guid;
			//   Storage.LoadObject(ref state);
			//   newobj.ObjectState = state;
			//   newobj.Initialize();
			//   return newobj;
			//});
		}

		// TODO Remove dups!! one bullet are cached
		public ModelBase GetObjectByID(Guid guid)
		{
			// EnsureTransactionOpenedAndValid();
			return Cache.GetValue(guid, () =>
			{
				var state = new ObjectState(this) { Identity = guid, };

				Storage.LoadObject(state);

				state.ClassMetainfo = DomainMetainfo.FindClass(state.ClassMetainfoDetected);
				if (state.ClassMetainfo == null)
				{
					throw new LightPersistException("Could not retrive meta information. Detected: " + state.ClassMetainfoDetected);
				}

				var model = CreateInstance(state.ClassMetainfo.TypeClr);
				model.ObjectState = state;
				model.Initialize();
				return model;
			});
		}

		public TransactionGuard BeginTransaction()
		{
			if (CurrentTransaction is NullTransaction)
			{
				// if it is a first transaction in the whole transaction cycle
				CurrentTransaction = new Transaction();
			}
			else
			{
				if (!CurrentTransaction.State.CanAddNested())
				{
					throw new TransactionException("Nested transaction can only be created in started transactions. Current transaction state: " + CurrentTransaction.State);
				}

				// it is a child transaction (nested transaction)
				CurrentTransaction = CurrentTransaction.AddChildTransaction(TransactionProcessor);
			}

			return new TransactionGuard(this, CurrentTransaction);
		}

		public void SetValue(ModelBase target, string propertyName, object newValue)
		{
			ObjectChangingGuard(target);

#if DEBUG
			var pi = target.ObjectState.ClassMetainfo.GetPropertyInfo(propertyName);
			if (pi != null)
			{
				Debug.Assert(!pi.IsReadonly);
			}
#endif

			EnsureTransactionOpenedAndValid();

			// store old value
			var oldValue = target.ObjectState.Get(propertyName);

			// send actual value to object
			target.ObjectState.Set(propertyName, newValue);

			// store action to the transaction
			var act = CreateAction_PropertyValue(target, propertyName, oldValue, newValue);
			CurrentTransaction.AddAction(act, TransactionProcessor);

			OnPrimitivePropertyChanged(target, propertyName, newValue);
		}

		static UniAction CreateAction_PropertyValue(ModelBase target, string propertyName, object oldValue, object newValue)
		{
			var act = UniAction.New(new { oldValue, newValue });
			act.TargetModel = target;
			act.PropertyName = propertyName;
			act.ActTypeToStringD = "Update";
			act.ToStringer = () => string.Format(": from '{0}' to '{1}'", act.Data.oldValue.ToStringDebug(), act.Data.newValue.ToStringDebug());

			act.DoModel = ctx => act.TargetModel.ObjectState.Set(act.PropertyName, act.Data.newValue);
			act.UndoModel = ctx => act.TargetModel.ObjectState.Set(act.PropertyName, act.Data.oldValue);

			act.DoComponents = ctx => ctx.ScheduleNotifyPropertyValue(act, act.Data.oldValue, act.Data.newValue);
			act.UndoComponents = ctx => ctx.ScheduleNotifyPropertyValue(act, act.Data.newValue, act.Data.oldValue);

			act.UndoStorage = act.DoStorage = ctx =>
			{
				if(ctx.Storage.IsExists(act.TargetModel.ObjectState))
				{
					ctx.Storage.UpdateObject(act.TargetModel.ObjectState);
				}
			};

			return act;
		}

		[Obsolete("Do not allow to bypass such access checking, as referencing transaction guard", true)]
		public void CommitTransaction()
		{
			EnsureTransactionOpenedAndValid();
			CommitTransaction(CurrentTransaction);
		}

		public void RollbackTransaction(TransactionGuard tr)
		{
			if (tr == null)
			{
				throw new ArgumentNullException("tr");
			}
			tr.RollbackTransaction();
		}

		internal void OnPropertyChanged(string proName)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(proName));
			}
		}

		void OnListRemoved(LightListCore lightList, int index, ModelBase itemToRemove)
		{
			foreach (var listener in _listeners)
			{
				listener.OnListRemoved(lightList, index, itemToRemove);
			}
		}

		void OnListRemoving(LightListCore lightList, int index, ModelBase itemToRemove)
		{
			foreach (var listener in _listeners)
			{
				listener.OnListRemoving(lightList, index, itemToRemove);
			}
		}

		void OnListInserted(LightListCore lightList, int index, ModelBase itemToInsert)
		{
			foreach (var listener in _listeners)
			{
				listener.OnListInserted(lightList, index, itemToInsert);
			}
		}

		void OnListInserting(LightListCore lightList, int index, ModelBase itemToInsert)
		{
			foreach (var listener in _listeners)
			{
				listener.OnListInserting(lightList, index, itemToInsert);
			}
		}

		void OnPrimitivePropertyChanged(ModelBase item, string property, object value)
		{
			foreach (var listener in _listeners)
			{
				listener.OnPrimitivePropertyChanged(item, property, value);
			}
		}

		ClassMetainfo ClrTypeToMetainfo(Type type)
		{
			return DomainMetainfo.FindClass(type);
		}

		void ObjectChangingGuard(ModelBase target)
		{
			if (TransactionProcessor.NotifyingInProgress)
			{
				throw new ObjectIsReadonlyException("Modifying object during notify is not allowed");
			}

			if (target.IsReadonly)
			{
				if (!OnObjectChangeRequest(target))
				{
					throw new ObjectIsReadonlyException();
				}
				// здесь будут танцы с бубном по поводу лока
				target.IsReadonly = false;
			}
		}

		internal object GetValue(ModelBase target, string name)
		{
			// EnsureTransactionOpenedAndValid();
			return target.ObjectState.Get(name);
		}

		/// <summary>
		/// Be carefull: this metod is called from ModelBase and ModelBase has a cache for resulting lists. 
		/// Use ModelBase.GetList when possible. Otherwise the list notification can be forgotten (because  
		/// another instance with subscribers exists).
		/// </summary>
		/// <param name="model"></param>
		/// <param name="propName"></param>
		/// <returns></returns>
		internal LightListCore GetList(ModelBase model, string propName)
		{
			//EnsureTransactionOpenedAndValid();

			// create new List<Reference> on demand. It could be done in ModelBase contructor?
			if (model.ObjectState.Get(propName) == null)
			{
				model.ObjectState.Set(propName, new List<Reference>());
			}

			//var elementsType = model.ObjectState.ClassMetainfo.GetPropertyInfo(propName).TypeClr;

			return new LightListCore(this, model, model.ObjectState.ClassMetainfo.GetPropertyInfo(propName)); //elementsType, propName);
		}

		internal void ListInsert(LightListCore lightList, int index, ModelBase itemToInsert)
		{
			string indent = null;

			ObjectChangingGuard(lightList.Owner);
			ObjectChangingGuard(itemToInsert);

			if (!TransactionProcessor.SkipCheckingAndLoggingTransaction)
			{
				EnsureTransactionOpenedAndValid();
				indent = CurrentTransaction.LoggingIndention;
			}

			try
			{
				Debug.WriteLine(string.Format(indent + "<ListInsert into {0}.{1} ← {2} >", lightList.Owner, lightList.PropertyName.TrimStart('_'), itemToInsert));

				//				if(!lightList.Owner.IsAlive)
				//				{
				//					throw new LightPersistException("You are inserting item into object that does not stored! It can broke down the object graph.");
				//				}

				OnListInserting(lightList, index, itemToInsert);

				if (!itemToInsert.IsAlive)
				{
					if (!lightList.IsStrongReferenceProperty())
					{
						throw new LightPersistException("you are inserting into weak property the object that is not strongly referenced by any other object");
					}

					var act = CreateAction_CreateDeleteObject(itemToInsert, true);

					CurrentTransaction.AddAction(act, TransactionProcessor);
				}

				InsertRemoveToList_AndStoreAction(lightList, itemToInsert, index, true);

				if (lightList.IsStrongReferenceProperty())
				{
					InsertRemoveToList_AndStoreAction(itemToInsert.StrongBackReferences, lightList, ModelBase.StrongBackReferenceListName, itemToInsert);
				}
				else
				{
					InsertRemoveToList_AndStoreAction(itemToInsert.WeakBackReferences, lightList, ModelBase.WeakBackReferenceListName, itemToInsert);
				}
				OnListInserted(lightList, index, itemToInsert);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.ToString());
				CurrentTransaction.State = TransactionState.Error;
				throw;
			}
			finally
			{
				Debug.WriteLine(indent + "</ListInsert>");
			}
		}

		static UniAction CreateAction_CreateDeleteObject(ModelBase item, bool isCreating)
		{
			var act = UniAction.New(new { item });

			act.TargetModel = item;
			act.ActTypeToStringD = isCreating ? "Create" : "Delete";
			act.ToStringer = () => act.Data.item.ToString();

			Action<TransactionProcessingContext> cacheAdd = ctx =>
			{
				//// if newer throws there, use Cache.Add to improove performance
				////Debug.Assert(!_context.Cache.ContainsKey(insertedAction.InsertedObject.ID));
				// TODO This is behavior for stability. Point to improove performance.
				ctx.Processor.LightPersistContext.Cache[act.TargetModel.ID] = act.Data.item;
			};

			Action<TransactionProcessingContext> cacheRemove = ctx => ctx.Processor.LightPersistContext.Cache.Remove(act.TargetModel.ID);

			if (isCreating)
			{
				act.BeforeDo = cacheAdd;
				act.AfterUndo = cacheRemove;
			}
			else
			{
				act.AfterDo = cacheRemove;
				act.BeforeUndo = cacheAdd;
			}

			Action<TransactionProcessingContext> add = ctx => ctx.Storage.CreateObject(act.Data.item.ObjectState);
			Action<TransactionProcessingContext> del = ctx => ctx.Storage.DeleteObject(act.Data.item.ObjectState);

			act.DoStorage = isCreating ? add : del;
			act.UndoStorage = isCreating ? del : add;

#if DEBUG
			act.DoModel = act.UndoModel = ctx => { Debug.Assert(ctx.Processor.SkipCheckingAndLoggingTransaction); };
#endif

			return act;
		}


		void InsertRemoveToList_AndStoreAction(LightListCore lightList, ModelBase itemToInsert, int index, bool isInsert)
		{
#if DEBUG
			var oldList = lightList.ReferenceList.ToArray();
#endif

			var reference = new Reference(itemToInsert.ID, itemToInsert.RealType);

			//lightList.ReferenceList.Insert(index, reference);

			//var act = new ListPropertyChangedAction(oldList, newList, lightList.PropertyName, lightList.Owner, lightList, itemToInsert, index, ListChangedAction.Insert);
			var action = UniAction.New(new ListActionData { List = lightList, Item = itemToInsert, Index = index, Reference = reference, IsInserting = isInsert });

			Action<TransactionProcessingContext> actInsert = x => action.Data.List.ReferenceList.Insert(action.Data.Index, action.Data.Reference);
			Action<TransactionProcessingContext> actRemove = x => action.Data.List.ReferenceList.RemoveAt(action.Data.Index);

			var modelDo = isInsert ? actInsert : actRemove;
			var modelUndo = !isInsert ? actInsert : actRemove;

			modelDo(null);

#if DEBUG
			var newList = lightList.ReferenceList.ToArray();
			action.ToStringer = () => string.Format(": from '{0}' to '{1}'", ToString(oldList), ToString(newList));
#endif

			action.ActTypeToStringD = "UpdateList";
			action.TargetModel = lightList.Owner;
			action.PropertyName = lightList.PropertyName;
			action.DoModel = modelDo;
			action.UndoModel = modelUndo;

			if(isInsert)
			{
				action.BeforeDo = ctx =>
				{
					if (!action.Data.IsValidated)
					{
						DagIntegrityChecker.CheckIntegrity(action.TargetModel);
						action.Data.IsValidated = true;
					}
				};
			}

			action.UndoStorage = action.DoStorage = ctx => ctx.Storage.UpdateObject(action.TargetModel.ObjectState);

			action.DoComponents = ctx =>
			{
				//ctx.AddToNotify(action);
				var propInfo = action.TargetModel.ObjectState.ClassMetainfo.GetPropertyInfo(action.PropertyName);
//				// Notify only known public properties, e.g. '__BackReferences' are excluded
				if (propInfo != null)
				{
//					// is this is public collection or it is public singleitem collection
					if (propInfo.IsList)
					{
						ctx.AddToNotifyActionList(action, action.Data);
					}
					else
					{
						var oldValue = action.Data.IsInserting ? null : action.Data.Item;
						var newValue = action.Data.IsInserting ? action.Data.Item : null;
						if (action.Data.IsInserting)
						{
							ctx.ScheduleNotifyPropertyValue(action, oldValue, newValue);
						}
						else
						{
							ctx.ScheduleNotifyPropertyValue(action, newValue, oldValue);
						}
					}
				}
			};

			action.UndoComponents = ctx =>
			{
				//ctx.AddToNotify(action);
				var propInfo = action.TargetModel.ObjectState.ClassMetainfo.GetPropertyInfo(action.PropertyName);
//				// Notify only known public properties, e.g. '__BackReferences' are excluded
				if (propInfo != null)
				{
//					// is this is public collection or it is public singleitem collection
					if (propInfo.IsList)
					{
						var rev = action.Data.Clone();
						rev.IsInserting = !rev.IsInserting;
						ctx.AddToNotifyActionList(action, rev);
					}
					else
					{
						var oldValue = !action.Data.IsInserting ? null : action.Data.Item;
						var newValue = !action.Data.IsInserting ? action.Data.Item : null;
						if (action.Data.IsInserting)
						{
							ctx.ScheduleNotifyPropertyValue(action, oldValue, newValue);
						}
						else
						{
							ctx.ScheduleNotifyPropertyValue(action, newValue, oldValue);
						}
					}
				}
			};

			CurrentTransaction.AddAction(action, TransactionProcessor);
		}

		/// <summary>
		/// Backreference from 'inserting item' to 'original list' owner
		/// </summary>
		/// <param name="list">Backreference list of inserting item</param>
		/// <param name="lightListOrigin">original list into which the insertion are processed now</param>
		/// <param name="listName">Item's backreference list name</param>
		/// <param name="listOwnerItemToInsert">Item that are inserting into original list</param>
		void InsertRemoveToList_AndStoreAction(List<BackReference> list, LightListCore lightListOrigin, string listName, ModelBase listOwnerItemToInsert)
		{
#if DEBUG
			var oldValue = list.ToArray();
#endif

			var item = new BackReference(lightListOrigin.Owner.ID, lightListOrigin.PropertyName);

			var act = UniAction.New(new { item, list });
			act.ActTypeToStringD = "UpdateBackRef";
			act.TargetModel = listOwnerItemToInsert;
			act.PropertyName = listName;

			Action<TransactionProcessingContext> modelDo = ctx => act.Data.list.Add(act.Data.item);
			Action<TransactionProcessingContext> modelUndo = ctx => act.Data.list.Remove(act.Data.item);

			modelDo(null);

#if DEBUG
			var newValue = list.ToArray();
			act.ToStringer = () => string.Format(": from '{0}' to '{1}'", ToString(oldValue), ToString(newValue));
#endif

			act.DoModel = modelDo;
			act.UndoModel = modelUndo;

			act.DoStorage = act.UndoStorage = ctx => ctx.Storage.UpdateObject(listOwnerItemToInsert.ObjectState);

			CurrentTransaction.AddAction(act, TransactionProcessor);
		}

		static string ToString(object value)
		{
			if (value is IEnumerable && !(value is string))
			{
				var col = ((IEnumerable)value).Cast<object>().ToArray();
				string collectionRow, prefix;
				prefix = col.Length + " items";
				if (col.Length > 7)
				{
					collectionRow = "...";
				}
				else if (col.Length > 0)
				{
					collectionRow = string.Join(", ", col.Select(x => ToString(x)).ToArray());
				}
				else
				{
					collectionRow = " ";
				}
				return string.Format("List of {0} [ {1}]", prefix, collectionRow);
			}

			return value == null ? "(null)" : value.ToStringDebug();
		}

		internal ModelBase GetListValue(LightListCore lightList, int index, Type type)
		{
			//EnsureTransactionOpenedAndValid();
			return GetObjectByID(lightList.ReferenceList[index].ID, type);
		}

		internal ModelBase GetListValue(LightListCore lightList, int index)
		{
			return GetObjectByID(lightList.ReferenceList[index].ID);
		}

		void RemoveFromList_AndStoreAction(List<BackReference> list, LightListCore lightListOrigin, string listName, ModelBase listOwnerItemToRemove)
		{
#if DEBUG
			var oldValue = list.ToArray();
#endif

			var item = new BackReference(lightListOrigin.Owner.ID, lightListOrigin.PropertyName);

			var act = UniAction.New(new { item, list });
			act.ActTypeToStringD = "UpdateBackRef";
			act.TargetModel = listOwnerItemToRemove;
			act.PropertyName = listName;

			Action<TransactionProcessingContext> modelDo = ctx => act.Data.list.Remove(act.Data.item);
			Action<TransactionProcessingContext> modelUndo = ctx => act.Data.list.Add(act.Data.item);
			modelDo(null);

#if DEBUG
			var newValue = list.ToArray();
			act.ToStringer = () => string.Format(": from '{0}' to '{1}'", ToString(oldValue), ToString(newValue)); ;
#endif

			act.DoModel = modelDo;
			act.UndoModel = modelUndo;

			act.DoStorage = act.UndoStorage = ctx => ctx.Storage.UpdateObject(listOwnerItemToRemove.ObjectState);

			CurrentTransaction.AddAction(act, TransactionProcessor);
		}

		internal void ListRemoveAt(LightListCore lightList, int index)
		{
			var itemToRemove = GetObjectByID(lightList.ReferenceList[index].ID);
			ListRemoveAt(lightList, index, itemToRemove);
		}

		internal void ListRemoveAt(LightListCore lightList, int index, Type elementType)
		{
			var itemToRemove = GetObjectByID(lightList.ReferenceList[index].ID, elementType);
			ListRemoveAt(lightList, index, itemToRemove);
		}

		internal void ListRemoveAt(LightListCore lightList, int index, ModelBase itemToRemove)
		{
			string indent = null;
			if (!TransactionProcessor.SkipCheckingAndLoggingTransaction)
			{
				EnsureTransactionOpenedAndValid();
				indent = CurrentTransaction.LoggingIndention;
			}

			ObjectChangingGuard(lightList.Owner);
			ObjectChangingGuard(itemToRemove);

			try
			{
				Debug.WriteLine(string.Format(indent + "<ListRemove from {0}.{1} → {2} >", lightList.Owner, lightList.PropertyName.TrimStart('_'), itemToRemove));

				OnListRemoving(lightList, index, itemToRemove);

				if (lightList.IsStrongReferenceProperty())
				{
					RemoveFromList_AndStoreAction(itemToRemove.StrongBackReferences, lightList, ModelBase.StrongBackReferenceListName, itemToRemove);

					InsertRemoveToList_AndStoreAction(lightList, itemToRemove, index, false);

					if (!itemToRemove.IsAlive)
					{
						// var oldWekRefList = itemToRemove.WeakBackReferences.ToList().AsReadOnly();
						if (itemToRemove.WeakBackReferences.Any())
						{
							foreach (var weak in itemToRemove.WeakBackReferences.ToArray())
							{
								var objWithWeakRef = GetObjectByID(weak.ID);
								var listWithWeakObj = objWithWeakRef.GetList(weak.PropertyName);
								var weakIndex = listWithWeakObj.IndexOf(itemToRemove);
								InsertRemoveToList_AndStoreAction(listWithWeakObj, itemToRemove, weakIndex, false);
							}

//							itemToRemove.WeakBackReferences.Clear();
//							var newWekRefList = itemToRemove.WeakBackReferences.ToList().AsReadOnly();
//							// clear Weak BackReference list
//							CurrentTransaction.AddAction(new PropertyChangedAction(oldWekRefList, newWekRefList, ModelBase.WeakBackReferenceListName, itemToRemove), TransactionProcessor);
						}

						// is this the last reference? if so, remove more nested reference first!
						itemToRemove.RemoveAllChildrens();

						var act = CreateAction_CreateDeleteObject(itemToRemove, false);

						CurrentTransaction.AddAction(act, TransactionProcessor);

						Cache.Remove(itemToRemove.ID);
					}
				}
				else
				{
					RemoveFromList_AndStoreAction(itemToRemove.WeakBackReferences, lightList, ModelBase.WeakBackReferenceListName, itemToRemove);
					InsertRemoveToList_AndStoreAction(lightList, itemToRemove, index, false);
				}

				OnListRemoved(lightList, index, itemToRemove);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.ToString());
				CurrentTransaction.State = TransactionState.Error;
				throw;
			}
			finally
			{
				Debug.WriteLine(indent + "</ListRemove>");
			}
		}

//		void RemoveFromList_AndStoreAction(LightListCore lightList, int index, ModelBase itemToRemove)
//		{
//			var oldList = lightList.ReferenceList.ToList().AsReadOnly();
//			lightList.ReferenceList.RemoveAt(index);
//			var newList = lightList.ReferenceList.ToList().AsReadOnly();
//
//			var updateAction = new ListPropertyChangedAction(oldList, newList, lightList.PropertyName, lightList.Owner, lightList, itemToRemove, index, ListChangedAction.Remove);
//			CurrentTransaction.AddAction(updateAction, TransactionProcessor);
//
//			//			lightList.Notify(itemToRemove, index, false);
//			//			updateAction.ProcessFlags |= ProcessFlags.UpdateUI;
//		}

		void Aspect()
		{
			if (_isDisposed)
			{
				throw new ObjectDisposedException(GetType().Name);
			}
		}

		void EnsureTransactionClosed()
		{
			Aspect();
			if (IsTransactionOpened)
			{
				throw new TransactionException("The transaction should be closed");
			}
		}

		void EnsureTransactionOpenedAndValid()
		{
			Aspect();
			if (!TransactionProcessor.SkipCheckingAndLoggingTransaction && !IsTransactionOpened)
			{
				throw new TransactionException("The transaction has not been opened");
			}
			var state = CurrentTransaction.State;
			if (state == TransactionState.Error || state == TransactionState.Unknown)
			{
				throw new TransactionException("The transaction is in invalid state: State = " + state);
			}
		}

		internal void RollbackTransaction(Transaction transaction)
		{
			Debug.AssertExists(transaction);

#if DEBUG
			if (!transaction.State.CanRollback())
			{
				throw new TransactionException("Transaction can not be rolled back. State = " + transaction.State);
			}
#endif

			TransactionProcessor.RollbackTransaction(transaction);
			// parent transaction is null for root
			CurrentTransaction = transaction.Parent;
		}

		internal void CommitTransaction(Transaction transaction)
		{
			#region Guards

			EnsureTransactionOpenedAndValid();

			if (transaction.State != TransactionState.Started)
			{
				throw new TransactionException("Transaction must be in Started state for allow to commit. State = " + transaction.State);
			}

#if DEBUG
			if (transaction == null)
			{
				throw new ArgumentNullException("transaction");
			}

			if (CurrentTransaction != transaction)
			{
				throw new TransactionException("This is not current transaction!!");
			}

#endif

			#endregion

			// close transaction if it's not root transaction
			if (CurrentTransaction.Parent != null)
			{
				CurrentTransaction.State = TransactionState.InnerCommitPostponed;
				CurrentTransaction = CurrentTransaction.Parent;
				return;
			}

			TransactionProcessor.CommitTransaction(CurrentTransaction);

			CurrentTransaction = null;
		}

		#region Model Creation

		#region Delegates

		public delegate ModelBase FactoryFunc(LightPersistContext ctx);

		#endregion

		static readonly Dictionary<Type, FactoryFunc> _mapFactory = new Dictionary<Type, FactoryFunc>();

		public T Create<T>() where T : ModelBase
		{
			return (T)CreateInternal(typeof(T));
		}

		internal object CreateInternal(Type type)
		{
			if(!typeof(ModelBase).IsAssignableFrom(type))
			{
				throw new Exception("Can create only model base types");
			}
			EnsureTransactionOpenedAndValid();

			var obj = CreateModel(type);
			Cache[obj.ID] = obj;

			// DbConfig.NewObjectsHolder.Add(obj);
			// CurrentTransaction.AddAction(new InsertedAction(obj, null, -1, null), TransactionProcessor);

			return obj;
		}

		ObjectState CreateObjectState(Type type)
		{
			return CreateObjectState(type, DebugHelper.NewGuid());
		}

		ObjectState CreateObjectState(Type type, Guid id)
		{
			return new ObjectState(this) { Identity = id, ClassMetainfo = ClrTypeToMetainfo(type) };
		}

		ModelBase CreateModel(Type type)
		{
			var model = CreateInstance(type);
			InitializeModel(model);
			return model;
		}

		internal void InitializeModel(ModelBase model)
		{
			model.ObjectState = CreateObjectState(model.GetType());
			model.Initialize();
		}

		ModelBase CreateModel(Type type, ObjectState state)
		{
			var model = CreateInstance(type);
			model.ObjectState = state;
			model.Initialize();
			return model;
		}

		public static void RegisterFactory<T>(FactoryFunc func)
		{
			_mapFactory.Add(typeof(T), func);
		}

		/// <summary>
		/// Very performance efficient solution
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		ModelBase CreateInstance(Type type)
		{
			// return (ModelBase)Activator.CreateInstance(type, new object[] {this});
			return _mapFactory[type](this);
		}

		#endregion
	}
}